/* TEAM AWESOMES
/* TEAM AWESOMES
 * Project 3: Reliable Data Transmission
 *
 * Authors:
 * -Kylan Hughes
 * -Leonardo Nguyen
 * -Anthony Kinsey
 *
 * The relsend application sends a file to the relrecv application reliably.
 * Implements stop-and-wait protocol with 2-byte sequence and acknowledgment values.
 * Includes timers for sender retransmission.
 */
 
#include "relsend.h"

// Debugging on/off
int debug = true;
// The socket file descriptor for this application.

// relrecv IP address.
char address[16];
// relrecv port number.
unsigned short relrecvPort; 
// Stores filename.
char filename[20]; 
// relsend port number.
unsigned short relsendPort = 4000;
// Message buffer to be sent to the receiver.
unsigned char sendMessage[BUFSIZE]; 

struct messageInfo msgInfo;

	
/**
 * The main function, which sends a file to waiting receiver.
 * 
 * @param argc The number of arugments.
 * @param argv The arguments.
 */
int main(int argc, char**argv) {
  
	/*******Configure Network Settings***********/
  
  //Designate protocol
  struct protoent * protocolEntry;
  if ((protocolEntry = getprotobyname("UDP")) == NULL){
    error("getprotobyname");
  }  
  
  //Sockaddr pointer to store address and port
  struct sockaddr * sap;
  
  //Create socket for sending and receiving
  if ((socketfd = socket(PF_INET, SOCK_DGRAM, protocolEntry->p_proto)) < 0){
    error("socket");
  }
  
  // Populate sockaddr pointer with address information.
  sap = configureHost(relsendPort);
  
  // Bind to the given socket and address for the relsend application.
  if(bind(socketfd, sap, sizeof(*sap)) != 0) {
    error("bind");
  }
	
  /********************************************/
	
	// Parse the command-line arguments.
	parseCommandLine(argc, argv);
	
	pthread_t tThread;
  int timeNum;
  timeNum = pthread_create(&tThread, NULL, timeThread, NULL);

	// Send a file to the receiver, starts the overall data transmission process.
  sendFile(socketfd, address, relrecvPort);
}

/**
 * Sends a message to a receiver.
 *
 * @param socketFileDescriptor The socket file descriptor that identifies the sender's binded port.
 * @param address The string representation (dot notation) of ip address of the intended receiver.
 * @param relrecvPort The listening port of the receiver application.
 */
void sendFile(int socketFileDescriptor, char * address, unsigned int relrecvPort) {
    		
	// Announce who we're sending data to.
  if (debug) { 
		printf("sendFile function: Sending %s to %s:%d\n", filename, address, relrecvPort); 
	}

	// Open the file that is to be transmitted.
  FILE * file = fopen(filename, "rb");
  if (file == NULL) {
    perror("Invalid File\n");
    exit(1);
  }

  // Get size of the file by pointing to the end of the file and retrieving 
	// the number of bytes between the beginning and the end of the file.
  fseek(file, 0, SEEK_END);
  long filesize = ftell(file);
	// Point to the beginning of the file.
  rewind(file);
		
	if (debug) {
		printf("sendFile function: The filesize is %ld\n", filesize);
  }
	
	fclose(file);
	
	//struct messageInfo msgInfo;
	
	// Choose some arbitrary initial sequence number.
	int initialSequenceNumber = 1000;
	
	// Initiate the transmission process by creating a SYN message and sending it to the receiver.
	createSynMessage(sendMessage, filename, initialSequenceNumber, filesize, &msgInfo);
	sendUDP(address, relrecvPort, socketfd, sendMessage, msgInfo.messageLength);	

	if (debug) {
		printf("sendFile function: SYN messageLength is %d\n", msgInfo.messageLength);
		printf("sendFile function: SYN messagePayloadSize is %d\n", msgInfo.messagePayloadSize);
	}
	
	currentSequenceNumber = initialSequenceNumber;
	expectedFinalSequenceNumber = (initialSequenceNumber + filesize);
	int moduloCount = 1;
	
	
	// Was testnig createDataMessage() function - DELTE THIS SECTION LATER
	createDataMessage(sendMessage, filename, initialSequenceNumber, BUFSIZE, 0, &msgInfo);
	sendUDP(address, relrecvPort, socketfd, sendMessage, msgInfo.messageLength);	
	addTimeout(currentSequenceNumber);

	if (debug) {
		printf("sendFile function: DATA messageLength is %d\n", msgInfo.messageLength);
		printf("sendFile function: DATA messagePayloadSize is %d\n", msgInfo.messagePayloadSize);
	}
	
	
	
	// Buffer for received messages.
	unsigned char receiveBuffer[BUFSIZE];
	  
  //Sockaddr pointer to store relrecv's address and port.
  struct sockaddr relrecvSap;
	
	// Wait for ACKs from relsend to determine retransmits or continued transmission; continue until
	// erroneous interruptions or until file transfer completes.
	//while ((currentSequenceNumber * moduloCount ) <= expectedFinalSequenceNumber) {
	
		int bytesReceived;
		if ((bytesReceived = receiveUDP(socketfd, receiveBuffer, sizeof(receiveBuffer), &relrecvSap)) > 0) {
    
			// Struct to store sender's address and port number
			struct addressPort relrecvInfo;
		
			// Enters information into struct that stores relrecv's address/port
			getAddressPort(&relrecvSap, &relrecvInfo);
		
			if (debug) {
				printf("sendFile function: Received from port %d: ", relrecvInfo.port);
				printMessage(receiveBuffer, bytesReceived);
				int i;
				for (i = 0; i < bytesReceived; i++) {
					printf("%c", receiveBuffer[i]);
				}
			}
		
			// Process the message, relsend is only concerned with ACK messages.
			if (receiveBuffer[0] == 0xCC && receiveBuffer[1] == 0xAF) {
				moduloCount += handleAckMessage(sendMessage, receiveBuffer, relrecvInfo, msgInfo);
			}
		}
	//}
}

/**
 * Parses the command-line specific for the relsend application.
 * This is a void function, a global variable for filename to be sent, 
 * receiver IP address, and receiver port number must be created to make
 * use of this function.
 *
 * NOTE: The global variable names are: filename, address, relrecvPort.
 *
 * @param argc The number of arguments.
 * @param argv The arguments passed on the command-line.
 */
void parseCommandLine(int argc, char ** argv) {

  // Validate the number of arguments pass on the command-line.
	// In addition to the main program, there should be a filename, receiver IP address,
	// and receiver port number.
  if (argc != 4) {
		printf("Invalid arguments, use format: ./awesomeprotocol <filename> <ip address> <port>\n");
    exit(1);
  }

  // Validate the filename to be sent to the receiver. Filenames only include alphanumeric values
	// and a single period to represent file extension.
  if (match(argv[1],"^[a-zA-Z0-9]+[\\.]?[a-zA-Z0-9]+$")) {
		strcpy(filename, argv[1]);
    if (debug) { 
			printf("++Command Line Arguments++\nFilename: %s\n", filename); 
		}
  }
	// Irregular filename.
  else {
    printf("Invalid filename, filename should only include characters a-z A-Z 0-9 and an extension.\n");
		exit(1);
  }

  // Validate the IP address of the receiver of the specified file. IP address values must be either
	// "localhost" or in dot notation.
  if (strcmp(argv[2], "localhost") == 0) {
    strcpy(address, argv[2]);
		if (debug) { 
			printf("IP Address: %s\n", address); 
		}
  }
  else if (match(argv[2],"^[1-9][0-9]{0,2}[\\.][0-9]{1,3}[\\.][0-9]{1,3}[\\.][0-9]{1,3}$")) {
		strcpy(address, argv[2]);  
		if (debug) { 
			printf("IP Address: %s\n", address); 
		}
  }
	// Irregular IP address; not in dot notation.
  else {
    printf("Invalid IP adress.\n");
    exit(1);
  }

  // Validate port number
  int tempPort;
  if (tempPort = atoi(argv[3])) {
    
		// Check the port range.
    if(tempPort < 1025 || tempPort > 65535) {
      printf("Invalid port, port must be in range 1025-65535.\n");
      exit(1);
    }
		// Valid port number.
    else {
			
			relrecvPort = tempPort;
      if (debug) { 
				printf("Port: %d\n", relrecvPort); 
			}
    }
  }
	// Failed atoi check.
  else { 
    printf("Invalid port: port must be a number.\n");
		exit(1);
  }
}

/**
 * Uses regular expression to determine if an array of characters matches the 
 * provided pattern.
 *
 * @param string The array of characters to match against a pattern.
 * @param pattern The regular expression pattern.
 * @return Returns a 1 if the "string" matches the "pattern", otherwise a 0.
 */
int match(const char * string, char * pattern) {

  int status;
  regex_t re;

  if (regcomp(&re, pattern, REG_EXTENDED | REG_NOSUB) != 0) {
    return 0;
  }

	// regexec returns a 0 if the string matches the pattern.
  status = regexec(&re, string, (size_t) 0, NULL, 0);
  regfree(&re);

	// If status isn't a 0, that means a match was not found.
  if (status != 0) {
		return 0;
  }
  return 1;
}